package org.road0329.dlt698.asn1.axdr;

import org.road0329.dlt698.DataUtils;
import org.road0329.dlt698.ReverseByteArrayInputStream;
import org.road0329.dlt698.ReverseByteArrayOutputStream;

public class AxdrInteger extends AxdrType {
    private boolean lengthFixed;
    private boolean unsigned;
    private long val = 0;
    private long maxVal = 0;
    private long minVal = 0;
    private byte[] dataCode = null;

    public AxdrInteger() {
        this.lengthFixed = false;
        this.unsigned = false;
    }

    public void setVal(int val) {
        this.lengthFixed = false;
        this.unsigned = false;
        this.setValue(val);
    }

    public void setDataCode(byte[] dataCode) {
        this.lengthFixed = false;
        this.unsigned = false;
        this.dataCode = dataCode;
    }

    public void setMinMaxVal(long min, long max, long val) {
        this.minVal = min;
        this.maxVal = max;
        this.setValue(val);
        this.lengthFixed = true;
        this.unsigned = (min >= 0);
    }

    public byte[] getDataCode() {
        return dataCode;
    }

    public int decode(ReverseByteArrayInputStream input) throws Exception {
        int codeLength;
        byte[] byteCode;
        int length;
        if (this.lengthFixed) {
            length = this.numOfBytesOf(this.minVal, this.maxVal);
            codeLength = length;
        } else {
            length = input.read();
            if ((length & 0x80) == 0x80) {
                length = (length ^ 0x80);
                codeLength = length + 1;
            } else {
                this.val = length;
                return 1;
            }
        }
        byteCode = new byte[length];
        DataUtils.readFully(input, byteCode, 0, byteCode.length);
        this.dataCode = byteCode;
        if ((byteCode[0] & 0x80) == 0x80 && !this.unsigned) {
            this.val = -1;
            for (int i = 0; i < length; i++) {
                int numShiftBits = 8 * (length - i - 1);
                this.val &= ((byteCode[i]) << numShiftBits) | ~(0xff << numShiftBits);
            }
        } else {
            this.val = 0;
            for (int i = 0; i < length; i++) {
                this.val |= (byteCode[i] & 0xff) << (8 * (length - i - 1));
            }
        }
        return codeLength;
    }

    public void encodeAndSave(int encodingSizeGuess) throws Exception {
        ReverseByteArrayOutputStream revOStream = new ReverseByteArrayOutputStream();
        revOStream.setBufSize(encodingSizeGuess);
        this.encode(revOStream);
        this.dataCode = revOStream.getArray();
    }

    public int encode(ReverseByteArrayOutputStream output) throws Exception {
        if (this.dataCode != null) {
            output.writeByte(this.dataCode);
            logEncodeMsg("AxdrInteger", output);
            return this.dataCode.length;
        } else {
            return this.encodeValue(output);
        }
    }

    public int encodeValue(ReverseByteArrayOutputStream output) {
        if (this.lengthFixed) {
            int codeLength = this.numOfBytesOf(this.minVal, this.maxVal);
            this.writeValToStream(output, codeLength);
            logEncodeMsg("AxdrInteger", output);
            return codeLength;
        } else {
            int codeLength = this.numOfBytes(this.val);
            this.writeValToStream(output, codeLength);
            output.write((codeLength & 0xff) | 0x80);
            logEncodeMsg("AxdrInteger", output);
            return codeLength + 1;
        }
    }

    private void writeValToStream(ReverseByteArrayOutputStream output, int codeLength) {
        for (int i = 0; i < codeLength; i++) {
            int value = (int)((this.val >> 8 * (i)) & 0xff);
            output.write(value);
        }
    }

    private int numOfBytesOf(long minVal, long maxVal) {
        return Math.max(this.numOfBytes(minVal), this.numOfBytes(maxVal));
    }

    private int numOfBytes(long val) {
        if(val == 0) {
            return 1;
        }
        long sVal = val;
        if (val < 0) {
            sVal = -val - 1;
        }
        return (int) Math.ceil((Math.floor(log2(sVal) + 1) / 8));
    }

    private double log2(long sVal) {
        return Math.log(sVal) / Math.log(2);
    }

    private void setValue(long val) {
        if (this.minVal > val) {
            throw new IllegalArgumentException("val is smaller than minimum");
        }
        if (this.maxVal < val) {
            throw new IllegalArgumentException("val is greater than maximum");
        }
        this.val = val;
    }

    public long getMax() {
        return this.maxVal;
    }

    public long getMin() {
        return this.minVal;
    }

    public long getValue() {
        return this.val;
    }

    @Override
    public String toString() {
        return String.valueOf(this.val);
    }

    public String toHexString() throws Exception {
        encodeAndSave(16);
        return DataUtils.bytesToHexString(this.dataCode);
    }
}



